跳至主要内容

Bash Shell 變數管理、特殊自動變數與空變數

FreeBSD Archlinux Photon_OS Oracle_linux WSL Docker

1. 變數定義與賦值規範

  • 基本定義

    VAR_NAME="value" # 等號兩側絕對不能有空格
    echo "${VAR_NAME}" # 建議一律使用大括號 ${} 包裹,防止邊界混淆
  • 指令輸出賦值 (Command Substitution)

    CURRENT_USER=$(whoami)
    BACKUP_DATE=$(date +%F)
  • 變數作用域

    • 一般宣告為局部變數(僅限當前行程)。

    • 透過 export VAR_NAME 宣告為環境變數,才能繼承給衍生(Fork)的子行程。

2. 特殊自動變數與位置參數速查表

變數名稱意義說明行為解析與工程師必知細節實用範例
$?上一個指令的退出狀態碼 (Exit Code)0 代表執行成功;非 0(如 1, 2, 127 等)代表執行失敗或異常systemctl restart nginx
[ $? -eq 0 ] && echo "OK"
$$當前 Shell 行程的 Process ID (PID)常用於腳本建立唯一的暫存檔名稱,避免並發衝突TMP_FILE="/tmp/run_$$.tmp"
$!最後一個進入背景執行的程式 PID抓取剛用 & 送入背景的行程 ID,方便後續用 wait 監控python3 job.py &
JOB_PID=$!
$-當前 Shell 所啟用的旗標 (Flags)顯示當前 shell 的執行選項(如 hBi 等互動模式標誌)echo "$-"
$0當前 Shell 腳本的檔名或路徑執行時輸入的指令路徑(如 ./deploy.sh/opt/deploy.shSCRIPT_DIR=$(dirname "$0")
$1 ~ $9命令列位置參數 (Positional Arguments)依序接收外部傳入的第一至第九個參數(第十個起需用 ${10}./backup.sh /data /mnt/nas
# $1=/data, $2=/mnt/nas
$#傳入腳本的參數總個數常用於腳本開頭校驗參數數量是否足夠[ $# -lt 2 ] && exit 1
$*所有傳入參數的集合 (合併字串)雙引號包覆 "$*" 時,所有參數以 $IFS(預設為空白)串為單一字串:"$1 $2 $3"echo "All args: $*"
$@所有傳入參數的集合 (獨立陣列)最常用於迴圈與轉發。雙引號 "$@" 會將各參數保留為獨立字串:"$1" "$2" "$3"for arg in "$@"; do ... done

3. 常見 Bash 內建環境變數

變數名稱說明典型用途
$BASH_ENV非互動式 Shell 啟動時自動讀取的設定檔路徑自動化 CI/CD 排程環境注入
$CDPATH使用 cd 指令時的搜尋目錄清單(冒號分隔)快速切換深層目錄
$LINENO當前正在執行的程式碼行號日誌追蹤與除錯:echo "Error at line $LINENO"
$LINES / $COLUMNS當前終端機畫面的高度 (列數) 與寬度 (行數)終端 UI / 進度條寬度自適應計算
$PPID當前行程的父行程 ID (Parent Process ID)檢查是誰呼叫了此腳本
$RANDOM隨機產生 0 至 32767 之間的整數生成隨機密碼或隨機埠號:PORT=$((10000 + RANDOM % 10000))
$SECONDS腳本啟動至今累計經過的秒數計算腳本耗時:echo "耗時: ${SECONDS}s"
$SHELL使用者預設偏好的 Shell 絕對路徑檢視預設 Shell(通常為 /bin/bash
$TMOUT無操作自動登出時間(單位:秒)安全加固:閒置指定秒數自動斷開終端連線

4. 空變數與未設定變數處理 (Parameter Expansion)

在 Shell 腳本中,如果直接使用未經賦值的變數,容易引發未預期行為(甚至引發如 rm -rf /${DIR} 誤刪根目錄的災難)。使用 Bash 的參數擴展語法能安全優雅地設定預設值或防呆退出:

語法格式條件判定處理行為實用範例
${var:-word}var 為空或未設定暫時使用 word 作為回傳值,不改變 var 本身內容PORT=${CUSTOM_PORT:-8080}
(未指定時預設走 8080)
${var:=word}var 為空或未設定word 賦值給 var,隨後使用新值${DATABASE_NAME:=homelab}
(未設定時自動定義並存入變數)
${var:?word}var 為空或未設定印出 word 錯誤訊息至 stderr,並立刻強制中斷終止程式DIR=${1:?"請提供目標目錄路徑!"}
(缺少參數直接中止運行)
${var:+word}var 已設定且非空暫時改用 word 取代它;若為空則回傳空值(存在檢測用)[ -n "${DEBUG:+x}" ] && echo "Debug on"

提示:如果省略冒號(例如 ${var-word}),則只有在 var 完全未宣告 (Unset) 時才會觸發;加上冒號(${var:-word})則包含 未宣告宣告了但值為空字串 "" 兩種情況。日常腳本一律推薦使用帶冒號的寫法。

5. 輸入型變數:read 指令進階用法

read 指令用於在終端機暫停並讀取使用者的鍵盤輸入:

# 基礎寫法
echo -n "請輸入專案名稱: "
read PROJECT_NAME
echo "您輸入的是: ${PROJECT_NAME}"

# 推薦的現代單行寫法 (結合 -p 提示、-t 超時、-s 密碼隱藏)
# 1. 帶提示字元 (-p)
read -p "請輸入資料庫名稱: " DB_NAME

# 2. 密碼隱藏輸入 (-s, 避免螢幕印出密碼)
read -sp "請輸入連線密碼: " DB_PASSWORD
echo "" # 密碼輸入完成手動換行

# 3. 逾時自動跳過 (-t,例如等待 10 秒)
read -t 10 -p "確認繼續執行?(y/N): " CONFIRM
CONFIRM=${CONFIRM:-N} # 逾時未輸入則預設為 N

6. 指令輸出代替變數與安全條件判斷實踐

將外部指令輸出存入變數後,進行比對時務必做好防呆包裝:

#!/usr/bin/env bash

# 1. 抓取目前執行者身份
CURRENT_USER=$(whoami)

# 2. 安全的條件判斷 (推薦 [[ ]] 結構,相容性與功能最完整)
if [[ "${CURRENT_USER}" == "root" ]]; then
echo "成功:當前是以 root 權限執行"
else
echo "警告:必須使用 root 權限執行此腳本 (當前使用者為: ${CURRENT_USER})" >&2
exit 1
fi

# 3. 檢查關鍵路徑變數(防禦性程式設計,避免未定義誤刪)
TARGET_DIR="${1:-}"

# 嚴格驗證避免 rm -rf /
if [[ -z "${TARGET_DIR}" ]]; then
echo "錯誤:未指定清理目錄!" >&2
exit 1
fi

echo "正在清理暫存資料夾: ${TARGET_DIR}..."